Skip to content

Cache launch_configuration per kernel - #605

Open
michel2323 wants to merge 1 commit into
mainfrom
cache-launch-configuration
Open

Cache launch_configuration per kernel#605
michel2323 wants to merge 1 commit into
mainfrom
cache-launch-configuration

Conversation

@michel2323

Copy link
Copy Markdown
Member

launch_configuration issues a zeKernelGetProperties round-trip. KernelAbstractions calls it on every dispatch of a kernel whose workgroupsize is DynamicSize and unspecified at the call site (src/oneAPIKernels.jl), so the query lands on the per-launch path rather than a setup path, and there it dominates.

Measured on a Data Center GPU Max 1550 with a no-op kernel (a[i] += 1f0, n=1024, best of 5 batches of 2000 launches):

launch path per launch
KA, dynamic workgroupsize (current) 9.63e-5 s
KA, workgroupsize passed at call site 1.99e-5 s
KA, kernel built StaticSize 9.69e-6 s
raw @oneapi, no KA 1.77e-5 s

A 9.9x spread, entirely host-side — the GPU does the same trivial work in every row. Launch-bound workloads pay this on every kernel they dispatch.

The change

The returned group size depends only on the kernel and its device. A ZeKernel's handle and module are fixed for its lifetime, and maxGroupSize / maxTotalGroupSize are static properties of the compiled kernel, so neither the ndrange nor any argument value can change the result. It is computed once per kernel and remembered.

Keyed weakly on the ZeKernel rather than on its raw handle: a handle is unique only among live kernels, so a handle-keyed entry could be inherited by a later kernel that reuses a destroyed kernel's address. A weak key ties the entry to the kernel's lifetime and needs no hook in oneL0's finalizer.

Effect, and what it does not fix

With the cache in place the same benchmark goes 9.63e-5 s -> 6.25e-5 s per launch, and the cache is confirmed to hold a single entry after 501 launches.

That is about a third of the gap. The remaining ~53 us is the re-partition and context rebuild that follow the query in the KA backend (src/oneAPIKernels.jl, the KA.partition / KA.mkcontext pair after the launch_configuration call), which this PR deliberately does not touch. Passing workgroupsize at the call site skips that whole branch and reaches 1.99e-5 s, so there is more to win there separately.

For reference, CUDA.jl's KA backend also calls launch_configuration per launch; the difference is that its occupancy API is cheap where zeKernelGetProperties is not. Caching here seemed preferable to changing every caller.

`launch_configuration` issues a `zeKernelGetProperties` round-trip, and
KernelAbstractions calls it on every dispatch of a kernel whose workgroupsize is
`DynamicSize` and unspecified at the call site (src/oneAPIKernels.jl). The query
therefore lands on the per-launch path, where it dominates: on a Data Center GPU
Max 1550, a no-op kernel costs 96 us per launch as-is and 9.7 us with the
workgroupsize pinned, a 9.9x difference that is entirely host-side. Launch-bound
workloads pay it on every kernel they dispatch.

The returned group size depends only on the kernel and its device. A ZeKernel's
handle and module are fixed for its lifetime, and maxGroupSize /
maxTotalGroupSize are static properties of the compiled kernel, so neither the
ndrange nor any argument value can change the result.

Keyed weakly on the ZeKernel rather than on its raw handle: a handle is unique
only among live kernels, so a handle-keyed entry could be inherited by a later
kernel that reuses a destroyed kernel's address. A weak key ties the entry to the
kernel's lifetime and needs no hook in oneL0's finalizer.

Measured with the cache in place: 96 us -> 62 us per launch. The remainder is the
re-partition and context rebuild that follow the query in the KernelAbstractions
backend, which this does not address.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Your PR requires formatting changes to meet the project's style guidelines.
Please consider running Runic (git runic main) to apply these changes.

Click here to view the suggested changes.
diff --git a/src/compiler/execution.jl b/src/compiler/execution.jl
index c3a7429..8df7b33 100644
--- a/src/compiler/execution.jl
+++ b/src/compiler/execution.jl
@@ -228,7 +228,7 @@ end
 # unique among *live* kernels, so a handle key could let a destroyed kernel's
 # entry be inherited by a later kernel that reuses its address. The weak key ties
 # the entry to the kernel's own lifetime and needs no hook in oneL0's finalizer.
-const _launch_config_cache = WeakKeyDict{ZeKernel,Int}()
+const _launch_config_cache = WeakKeyDict{ZeKernel, Int}()
 
 function launch_configuration(kernel::HostKernel{F,TT}) where {F,TT}
     fun = kernel.fun
@@ -239,7 +239,7 @@ function launch_configuration(kernel::HostKernel{F,TT}) where {F,TT}
     return config
 end
 
-function _launch_configuration_uncached(kernel::HostKernel{F,TT}) where {F,TT}
+function _launch_configuration_uncached(kernel::HostKernel{F, TT}) where {F, TT}
     # Level Zero's zeKernelSuggestGroupSize provides a launch configuration
     # that exactly cover the input size. This can result in very awkward
     # configurations, so roll our own version that behaves like CUDA's

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant